package com.wuwei.elearning.utils;

import com.alibaba.excel.util.StringUtils;
import com.wuwei.elearning.config.MinioConfig;
import com.wuwei.elearning.enumPackage.Constant;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jodconverter.core.DocumentConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class MinioUtil {
    @Autowired
    private MinioConfig prop;

    @Resource
    private MinioClient minioClient;

//    @Autowired
//    private CodeService codeService;
@PostConstruct
public void init() {
    try {
        log.info("Initializing MinIO connection to endpoint: {}", prop.getEndpoint());

        // 首先测试连接
        minioClient.listBuckets();
        log.info("Successfully connected to MinIO server");

        // 检查 bucket 是否存在
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(prop.getBucketName())
                .build());

        if (!found) {
            log.info("Bucket {} not found, creating new bucket", prop.getBucketName());
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(prop.getBucketName())
                    .build());
            log.info("Successfully created bucket: {}", prop.getBucketName());
        } else {
            log.info("Bucket {} already exists", prop.getBucketName());
        }
    } catch (Exception e) {
        log.error("Failed to initialize MinIO: endpoint={}, bucket={}, error={}",
                prop.getEndpoint(), prop.getBucketName(), e.getMessage(), e);
        throw new RuntimeException("Could not initialize MinIO bucket: " + e.getMessage(), e);
    }
}


    /**
     * 查看存储bucket是否存在
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 上传pdf文件到MinIO服务器
     * @param file 要上传的文件
     * @return 上传后的文件路径
     * @throws IOException 如果文件操作出错
     */
    @SneakyThrows
    public String uploadPdf(File file,String  fileName) {
        String objectPath = Constant.PDF+"/"+fileName;
        if (!file.exists()) {
            throw new RuntimeException("File not exists: " + file.getAbsolutePath());
        }
        // 上传文件
        minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(prop.getBucketName())
                        .object(objectPath)
                        .filename(file.getAbsolutePath())
                        .contentType("application/octet-stream") // 可以根据文件类型设置适当的MIME类型
                        .build()
        );

        // 构建并返回文件访问路径
        String fileUrl = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .bucket(prop.getBucketName())
                        .object(objectPath)
                        .method(Method.GET)
                        .expiry(7, TimeUnit.DAYS) // 设置URL有效期
                        .build()
        );
        return fileUrl;
    }

    /**
     * 创建存储bucket
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 删除存储bucket
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 文件上传
     *
     * @param file 文件
     * @return Boolean
     */
    public String upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)){
            throw new RuntimeException();
        }
        String fileName = UUIDUtil.getUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = TimeUtils.getNowDateLongStr("yyyy-MM/dd") + "/" + fileName;
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 预览图片
     * @param fileName
     * @return
     */
    public String preview(String fileName){
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 文件下载
     * @param fileName
     */
    @SneakyThrows
    public String download(String fileName) {
        // 动态生成目标路径
        String templatesDir = System.getProperty("user.dir") + "\\src\\main\\resources\\static\\ppt";
        Path targetPath = Paths.get(templatesDir);

        // 确保目录存在
        if (!Files.exists(targetPath)) {
            Files.createDirectories(targetPath);
            //删除目录下所有文件
        }



        String uuid = UUIDUtil.getUUID();

        // 构建完整文件路径
        String filePath = templatesDir + "\\"+uuid+".pptx";
        File file = new File(filePath);

        // 删除旧文件（如果存在）
        if (file.exists()) {
            file.delete();
        }



        // 配置Minio下载参数
        DownloadObjectArgs objectArgs = DownloadObjectArgs.builder()
                .bucket(prop.getBucketName())
                .object(fileName)
                .filename(filePath)
                .build();

        minioClient.downloadObject(objectArgs);
        return uuid;
    }

    @SneakyThrows
    public void download(String fileName, HttpServletResponse res) {
        try {
            // 1. 获取元数据
            StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(prop.getBucketName())
                            .object(fileName)
                            .build());

            // 2. 设置MIME类型
            String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
            String contentType;
            switch (extension) {
                case "pptx":
                    contentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                    break;
                case "ppt":
                    contentType = "application/vnd.ms-powerpoint";
                    break;
                default:
                    contentType = Files.probeContentType(Paths.get(fileName));
                    if (contentType == null) {
                        contentType = "application/octet-stream";
                    }
            }
            res.setContentType(contentType);

            // 3. 处理文件名
            String displayFileName = fileName.substring(fileName.lastIndexOf('/') + 1)
                    .replace(" ", "_")
                    .replaceAll("[^a-zA-Z0-9-_.]", "");
            String encodedFileName = URLEncoder.encode(displayFileName, String.valueOf(StandardCharsets.UTF_8))
                    .replaceAll("\\+", "%20");
            res.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
            res.setHeader("Content-Length", String.valueOf(stat.size()));
            res.setHeader("Cache-Control", "no-store");

            // 4. 流传输
            try (InputStream is = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(prop.getBucketName())
                            .object(fileName)
                            .build());
                 OutputStream os = res.getOutputStream()) {

                byte[] buffer = new byte[8192];
                int totalBytes = 0;
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                    totalBytes += bytesRead;
                }

                // 5. 完整性检查
                if (totalBytes != stat.size()) {
                    log.error("文件大小不匹配: 实际={}, 预期={}", totalBytes, stat.size());
                    if (!res.isCommitted()) {
                        res.resetBuffer();
                        res.sendError(500, "文件传输失败");
                    }
                    return;
                }
            }

        } catch (ErrorResponseException e) {
            if (e.response().code() == 404) {
                res.sendError(404, "文件不存在: " + fileName);
            } else {
                log.error("MinIO错误: {}", fileName, e);
                res.sendError(500, "服务器存储错误");
            }
        } catch (Exception e) {
            log.error("下载失败: {}", fileName, e);
            if (!res.isCommitted()) {
                res.sendError(500, "下载异常");
            }
        }
    }




    /**
     * 查看文件对象
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     * @param fileName
     * @return
     * @throws Exception
     */
    public boolean remove(String fileName){
        try {
            minioClient.removeObject( RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
        }catch (Exception e){
            return false;
        }
        return true;
    }

    /**
     * 获取文件预览URL
     * @param objectName 文件路径
     * @param expires URL过期时间（秒）
     * @return 预签名的URL
     */
    public String getPreviewUrl(String objectName, int expires) {
        try {
            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                    .bucket(prop.getBucketName())
                    .object(objectName)
                    .method(Method.GET)
                    .expiry(expires, TimeUnit.SECONDS)
                    .build();
            String fullUrl = minioClient.getPresignedObjectUrl(args);

            // 从完整URL中提取相对路径
            try {
                URL url = new URL(fullUrl);
                return url.getPath() + "?" + url.getQuery();
            } catch (MalformedURLException ex) {
                log.error("Failed to parse URL: {}", fullUrl, ex);
                return null;
            }
        } catch (Exception e) {
            log.error("Failed to get preview url for object: {}", objectName, e);
            return null;
        }
    }

    /**
     * 获取临时访问URL（默认7天有效期）
     */
    public String getPreviewUrl(String objectName) {
        return getPreviewUrl(objectName, 7 * 24 * 3600); // 7天
    }

    @SneakyThrows
    public String getPresignedObjectUrl (String objectName) {
        Map<String, String> reqParams = new HashMap<String, String>();
        reqParams.put("response-content-type", "application/json");

        String url =
                minioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.GET)
                                .bucket(prop.getBucketName())
                                .object(objectName)
                                .expiry(2, TimeUnit.HOURS)
                                .extraQueryParams(reqParams)
                                .build());
        System.out.println(url);
        return url;
    }

    /**
     * 按照不同的类型把文件存放到指定的路径
     * @param file 文件
     * @param path 路径
     * @return
     */
    public void uploadToPath(MultipartFile file,String path) throws Exception {
        if (StringUtils.isBlank(path)) {
            throw new RuntimeException();
        }
        PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(path)
                .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
        //文件名称相同会覆盖
        minioClient.putObject(objectArgs);
    }

    /**
     * 按照不同的类型把文件存放到不同的目录
     * @param file
     * @param type
     * @return
     */
    public String upload(MultipartFile file,String type) throws Exception {
        String originalFilename = type+"/"+file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)){
            throw new RuntimeException();
        }
        PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(originalFilename)
                .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
        //文件名称相同会覆盖
        minioClient.putObject(objectArgs);
        return originalFilename;
    }


    /**
     * 获取指定路径下所有图片
     * @param directoryPath
     * @return
     * @throws Exception
     */
    public List<String> getDirectoryImageUrls( String directoryPath) throws Exception {
        List<String> imageUrls = new ArrayList<>();

        // 创建MinIO客户端
        MinioClient minioClient = MinioClient.builder()
                .endpoint(prop.getEndpoint())
                .credentials(prop.getAccessKey(), prop.getSecretKey())
                .build();

        // 确保目录路径以 / 结尾
        if (!directoryPath.endsWith("/")) {
            directoryPath += "/";
        }

        // 列出指定目录下的所有对象
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(prop.getBucketName())
                        .prefix(directoryPath)
                        .recursive(false)  // false表示不递归查询子目录
                        .build()
        );

        // 遍历所有对象
        for (Result<Item> result : results) {
            Item item = result.get();
            String objectName = item.objectName();

            // 检查是否为图片文件（通过文件扩展名）
            if (isImageFile(objectName)) {
                // 获取预签名URL，过期时间设置为7天
                String url = minioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .bucket(prop.getBucketName())
                                .object(objectName)
                                .method(Method.GET)
                                .expiry(7, TimeUnit.DAYS)
                                .build()
                );
                imageUrls.add(url);
            }
        }

        return imageUrls;
    }

    // 判断文件是否为图片
    private boolean isImageFile(String fileName) {
        String[] imageExtensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp"};
        fileName = fileName.toLowerCase();
        for (String ext : imageExtensions) {
            if (fileName.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }

    @SneakyThrows
    public GetObjectResponse getObject(String fileName) {
        GetObjectResponse object = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(prop.getBucketName())
                        .object(fileName)
                        .build()
        );
        return object;
    }

    /**
     * 从 MinIO 下载 Word 文档并转换为 PDF
     * @param fileName MinIO 中的文件名
     * @return 转换后的 PDF 输入流
     */
    @SneakyThrows
    public InputStream convertWordToPdf(String fileName) {
        // 从 MinIO 获取对象
        GetObjectResponse wordFile = getObject(fileName);

        // 创建临时文件
        File tempInputFile = File.createTempFile("minio-word-", ".docx");
        File tempOutputFile = File.createTempFile("minio-pdf-", ".pdf");

        try {
            // 将 MinIO 输入流写入临时文件
            try (FileOutputStream fos = new FileOutputStream(tempInputFile);
                 InputStream is = wordFile) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }

            // 使用 JODConverter 进行转换
            documentConverter.convert(tempInputFile)
                    .to(tempOutputFile)
                    .execute();

            // 将转换后的 PDF 读入内存，并返回输入流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try (FileInputStream fis = new FileInputStream(tempOutputFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesRead);
                }
            }

            return new ByteArrayInputStream(baos.toByteArray());

        } finally {
            // 清理临时文件
            if (tempInputFile.exists()) {
                tempInputFile.delete();
            }
            if (tempOutputFile.exists()) {
                tempOutputFile.delete();
            }
        }
    }

    @Resource
    private DocumentConverter documentConverter;

    /**
     * 直接将 Word 文档转换为 PDF 并写入输出流
     * 适用于在 Controller 中直接将 PDF 写入 HttpServletResponse
     */
    @SneakyThrows
    public void convertWordToPdfAndWrite(String fileName, OutputStream outputStream) {
        // 从 MinIO 获取对象
        GetObjectResponse wordFile = getObject(fileName);

        // 创建临时文件
        File tempInputFile = File.createTempFile("minio-word-", ".docx");
        File tempOutputFile = File.createTempFile("minio-pdf-", ".pdf");

        try {
            // 将 MinIO 输入流写入临时文件
            try (FileOutputStream fos = new FileOutputStream(tempInputFile);
                 InputStream is = wordFile) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }

            // 使用 JODConverter 进行转换
            documentConverter.convert(tempInputFile)
                    .to(tempOutputFile)
                    .execute();

            // 将 PDF 直接写入提供的输出流
            try (FileInputStream fis = new FileInputStream(tempOutputFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

        } finally {
            // 清理临时文件
            if (tempInputFile.exists()) {
                tempInputFile.delete();
            }
            if (tempOutputFile.exists()) {
                tempOutputFile.delete();
            }
        }
    }


    @SneakyThrows
    public void convertPptToPdf(String fileName, OutputStream outputStream) {
        // 从 MinIO 获取对象
        GetObjectResponse wordFile = getObject(fileName);

        // 创建临时文件
        File tempInputFile = File.createTempFile("minio-apache-ppt-", ".pptx");
        File tempOutputFile = File.createTempFile("minio-apache-pdf-", ".pdf");

        try {
            // 将 MinIO 输入流写入临时文件
            try (FileOutputStream fos = new FileOutputStream(tempInputFile);
                 InputStream is = wordFile) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }

            // 使用 JODConverter 进行转换
            documentConverter.convert(tempInputFile)
                    .to(tempOutputFile)
                    .execute();

            // 将 PDF 直接写入提供的输出流
            try (FileInputStream fis = new FileInputStream(tempOutputFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

        } finally {
            // 清理临时文件
            if (tempInputFile.exists()) {
                tempInputFile.delete();
            }
            if (tempOutputFile.exists()) {
                tempOutputFile.delete();
            }
        }
    }

    /**
     * 从MinIO下载文件到本地
     *
     * @param minioPath MinIO中的文件路径
     * @param targetFile 要下载到的本地文件
     */
    public void downloadFile(String minioPath, File targetFile) {
        try {
            // 确保目标文件的父目录存在
            if (targetFile.getParentFile() != null && !targetFile.getParentFile().exists()) {
                targetFile.getParentFile().mkdirs();
            }

            // 从MinIO获取对象
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(prop.getBucketName())
                    .object(minioPath)
                    .build();

            // 下载文件到本地
            try (InputStream inputStream = minioClient.getObject(getObjectArgs);
                 FileOutputStream outputStream = new FileOutputStream(targetFile)) {

                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }

                log.info("文件从MinIO下载成功: {}", minioPath);
            }
        } catch (MinioException e) {
            log.error("MinIO下载文件失败: {}", minioPath, e);
            throw new RuntimeException("从MinIO下载文件失败", e);
        } catch (IOException e) {
            log.error("IO异常，下载文件失败: {}", minioPath, e);
            throw new RuntimeException("下载文件IO异常", e);
        } catch (Exception e) {
            log.error("下载文件发生未知异常: {}", minioPath, e);
            throw new RuntimeException("下载文件发生未知异常", e);
        }
    }
}
